# -*- coding: utf-8 -*-
# @Author  : LvShenkai
# @Time    : 2021/9/14 11:36
# @Function: 输入二维数组，使用k-means进行聚类
import datetime

import pandas
from sklearn.cluster import KMeans
import numpy
from scipy.spatial.distance import cdist

from dataload.dataset import DataSet
from parameter.parameter import Parameter


class Cluster_tool:
    """
        聚类工具类
    """

    def __init__(self, df_data: pandas.DataFrame):
        """
            构造函数
        :param df_data: 输入数据，DataFrame形式
        """
        # 数据处理，转成DataFrame类型
        self.df_data = df_data

    def k_means(self, num_center: int = 3, start: int = 1, end: int = None, columns=None, OMP_NUM_THREADS=3):
        """
            进行k-means聚类
        # :param index: 聚类数据范围名，如用1-3列数据聚类，则可命名index='1-3'
        :param num_center: 聚类中心数量
        :param start: 聚类数据的起始列
        :param end: 聚类数据的终点列
        :param columns: 聚类数据的列名数组，如果不为空，则优先使用这些列作为需要聚类的列项
        :param OMP_NUM_THREADS: 最大线程数
        :return:返回修改后的df_data和各个聚类中心,以DataFrame格式返回
        """
        # 数据进行规一化
        df_data = self.df_data.copy(deep=True)
        df_data = DataSet.normalization(df_data)

        # 取出需要聚类的所有列数据
        if columns is None:  # 列名数组没有给出，则按下标取列名
            columns = ['DOW']
            columns.extend(list(df_data)[start:end])

        df = df_data[columns]
        index = int(Parameter.INDEX_DIC.value[columns[-1]])  # 索引名

        # 聚类中心进行列名转换的字典
        dic = dict()
        for i, column in enumerate(columns):
            dic[i] = column
        # 调用k-means包进行聚类
        clf = KMeans(n_clusters=num_center, OMP_NUM_THREADS=OMP_NUM_THREADS)
        cluster_result = clf.fit(df.values)  # 聚类结果

        df_data.insert(1, 'cluster_label', cluster_result.labels_)  # 各行数据的聚类标签(即所属聚类中心),插入到最前面
        df_data.insert(1, 'index', index)  # 聚类数据范围名(即索引)，插入到最前面列

        # 处理聚类中心，存为DataFrame格式
        cluster_centers = cluster_result.cluster_centers_  # 取出聚类中心
        df_cluster_centers = pandas.DataFrame(cluster_centers)  # 转为DataFrame
        df_cluster_centers.rename(columns=dic, inplace=True)  # 修改列名

        # 添加DCP0的均值和方差到df_cluster_centers中

        # 需要用规一化前的数据去求平均值和方差，故需要对数据和聚类中心进行反规一化
        df_cluster_centers = DataSet.reverse_normalization(df_cluster_centers)
        df_data = DataSet.reverse_normalization(df_data)

        DN1_mean = [0.0 for _ in range(num_center)]  # 每个类别的均值
        DN1_std = [0.0 for _ in range(num_center)]  # 每个类别的方差
        for cluster_label in range(num_center):  # 遍历每个聚类中心，添加mean和std
            DN1 = []  # 保存所有该类数据的DN1项
            for _, row in df_data.iterrows():
                if row['cluster_label'] == cluster_label:
                    DN1.append(row['ITTLN1'])
            if len(DN1) > 0:
                DN1 = numpy.array(DN1)
                DN1_mean[cluster_label] = DN1.mean()
                DN1_std[cluster_label] = DN1.std()
        df_cluster_centers['DN1_mean'] = DN1_mean
        df_cluster_centers['DN1_std'] = DN1_std

        # 聚类中心中插入'cluster_label'、'index'
        df_cluster_centers.insert(0, 'cluster_label', range(num_center))  # 各行数据的聚类标签(即所属聚类中心),插入到最前面
        df_cluster_centers.insert(0, 'index', index)  # 聚类数据范围名(即索引)，插入到最前面列

        # print('\033[33m均值\033[0m', DN1_mean)
        # print('\033[33m标准差\033[0m', DN1_std)
        # print('\033[33m未修改数据\033[0m')
        # print(self.df_data)
        # print('#' * 100)
        # print('\033[33m修改数据\033[0m')
        # print(df_data)
        # print('#' * 100)
        # print('\033[33m聚类中心list版\033[0m')
        # print(cluster_centers)
        # print('#' * 100)
        # print('\033[33m聚类中心df版\033[0m')
        # print(df_cluster_centers)

        # 聚类中心数据再进行规一化
        # df_cluster_centers = DataSet.normalization(df_cluster_centers)

        return df_data, df_cluster_centers

    def elbow_rule(self, max_num_center: int = 20, start: int = 1, end: int = None, columns=None):
        """
            通过肘部法则确定最佳聚类中心个数
        :param max_num_center: 最大的聚类中心个数
        :param start: 数据开始项下标
        :param end: 数据结束项下标
        :param columns: 实际数据列名
        :return: 返回最佳聚类中心数，以及各个聚类个数的畸变值
        """
        # 取出需要聚类的所有列数据
        if columns is None:  # 列名数组没有给出，则按下标取列名
            columns = list(self.df_data)[start:end]
        df = self.df_data[columns]
        # 聚类中心进行列名转换的字典
        dic = dict()
        for i, column in enumerate(columns):
            dic[i] = column

        data_np = df.values  # 数据

        # 判断最佳聚类中心
        elbow_result = []  # 各个聚类中心的畸变值,聚类个数为0的用0.0值占位
        # 遍历各聚类中心，得到各个聚类中心的畸变值
        for num_center in range(1, max_num_center + 1):
            # 调用k-means包进行聚类
            clf = KMeans(n_clusters=num_center)
            clf.fit(data_np)  # 聚类结果
            value = sum(numpy.min(cdist(data_np, clf.cluster_centers_, 'euclidean'), axis=1)) / data_np.shape[0]
            elbow_result.append(value)

        # 畸变值随着聚类中心的增加的改变值
        cha = [elbow_result[i] - elbow_result[i + 1] for i in range(len(elbow_result) - 1)]
        # 畸变值变化最大的值
        max_cha = max(cha)
        # 最大差值的下标
        max_cha_index = cha.index(max_cha)
        # 肘部图中拐点的畸变值
        a_v = elbow_result[max_cha_index + 1]
        # 最佳的聚类中心个数。
        # 畸变值拐点的下标+1，则是最佳的聚类中心个数(注:数组下标0存的是聚类中心个数为1的畸变值)
        best_center_num = elbow_result.index(a_v) + 1
        # print(max(cha), a_v, index)

        return best_center_num, elbow_result


if __name__ == '__main__':
    data_list_dic_1 = [
        {'FLIGHTDATE': datetime.datetime(2018, 7, 19, 0, 0),
         'ITTL360': None, 'ITTL180': None, 'ITTL90': None, 'ITTL55': None, 'ITTL45': None, 'ITTL35': None,
         'ITTL28': None, 'ITTL25': None, 'ITTL22': None, 'ITTL19': None, 'ITTL17': None, 'ITTL15': None,
         'ITTL13': None, 'ITTL11': None, 'ITTL9': None, 'ITTL8': None, 'ITTL7': None, 'ITTL6': None,
         'ITTL5': None, 'ITTL4': None, 'ITTL3': None, 'ITTL2': 1, 'ITTL1': 5, 'ITTL0': 10,
         'ITTLN1': 356},
        {'FLIGHTDATE': datetime.datetime(2018, 7, 21, 0, 0),
         'ITTL360': None, 'ITTL180': None, 'ITTL90': None, 'ITTL55': None, 'ITTL45': None, 'ITTL35': None,
         'ITTL28': None, 'ITTL25': None, 'ITTL22': None, 'ITTL19': None, 'ITTL17': None, 'ITTL15': None,
         'ITTL13': None, 'ITTL11': None, 'ITTL9': None, 'ITTL8': None, 'ITTL7': None, 'ITTL6': None,
         'ITTL5': None, 'ITTL4': None, 'ITTL3': None, 'ITTL2': 4, 'ITTL1': 6, 'ITTL0': 8,
         'ITTLN1': 316},
        {'FLIGHTDATE': datetime.datetime(2018, 7, 24, 0, 0),
         'ITTL360': None, 'ITTL180': None, 'ITTL90': None, 'ITTL55': None, 'ITTL45': None, 'ITTL35': None,
         'ITTL28': None, 'ITTL25': None, 'ITTL22': None, 'ITTL19': None, 'ITTL17': None, 'ITTL15': None,
         'ITTL13': None, 'ITTL11': None, 'ITTL9': None, 'ITTL8': None, 'ITTL7': None, 'ITTL6': None,
         'ITTL5': None, 'ITTL4': None, 'ITTL3': None, 'ITTL2': None, 'ITTL1': 56, 'ITTL0': 111,
         'ITTLN1': 216},
        {'FLIGHTDATE': datetime.datetime(2018, 7, 25, 0, 0),
         'ITTL360': None, 'ITTL180': None, 'ITTL90': None, 'ITTL55': None, 'ITTL45': None, 'ITTL35': None,
         'ITTL28': None, 'ITTL25': None, 'ITTL22': None, 'ITTL19': None, 'ITTL17': None, 'ITTL15': None,
         'ITTL13': None, 'ITTL11': None, 'ITTL9': None, 'ITTL8': None, 'ITTL7': None, 'ITTL6': None,
         'ITTL5': None, 'ITTL4': None, 'ITTL3': 1, 'ITTL2': 23, 'ITTL1': 67, 'ITTL0': 115,
         'ITTLN1': 218}
    ]
    data = DataSet.data_load(data_list_dic_1)
    a = Cluster_tool(data)
    a.elbow_rule(max_num_center=4, start=1, end=24)
